<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Map</title>
</head>
<body>
  <div id="myDiv">1</div>
  <div>2</div>
  <div>3</div>
  <div>4</div>
  <div>5</div>
  <script>
    //javascript的对象，本质上是键值对的集合(Hash结构)，但是传统上只能用字符串当做键。这给它的使用带来了很大的限制。
    var data = {};
    var element = document.getElementById('myDiv');
    console.log('element', element) // <div id="myDiv">1</div>
    data[element] = 'metedata';

    console.log('data', data) // [object HTMLDivElement]: "metedata"
    // 上面代码的原意是将一个DOM节点作为对象data的键，但是由于对象只接受字符串作为键名，
    // 所以element被自动转为字符串[object HTMLDivElement]
    /**
     * 为了解决这个问题，es6提供了Map数据结构，它类似于对象，也是键值对的集合
     * 但是“键”的范围不限于字符串，各种类型的值(包括对象),都可以当做键，也就是说
     * Object结构提供 字符串 -- 值 的对应
     * Map结构 提供 值--值 的对应，是一种更完善的Hash结构实现，
     * 如果你需要“键值对”的数据结构，Map比Object更合适
    */
   const nodeMap = new Map()
   nodeMap.set(element, '元素节点值')
   console.log('nodeMap', nodeMap)
   // 取key
   console.log(nodeMap.get(element)) // 元素节点值

   var m = new Map();
   var o = {P: 'Hello World'};
   m.set(o, 'content')
   m.get(o)// 'content'
   m.has(o); // true
   console.log('m',m)
   m.delete(o);
   m.has(o);//false
   console.log('m',m)



   const obj1 = {age:18, id: 11}
   console.log('obj1--',obj1)
   delete obj1.age
   console.log('obj1',obj1)
   /**
    * 上面代码使用Map结构的set方法，将对象o当做m的一个键，然后又用get方法读取这个键，接着使用delete方法删除了这个键
    * 上面的例子展示了如何向Map添加成员，作为构造函数，Map也可以接受一个数组作为参数，该数组的成员是一个个表示键值对的数组。
   */
  var map = new Map([
    ['name', '张三','ss'],
    ['title', 'Anthor']
  ]);
  map.size; // 2
  console.log('map',map)
  map.has('name');// true
  map.get('name');// '张三'
  map.has('title'); // true
  map.get('title'); // Author
  // 上面代码再新建Map实例时，就指定了俩个键name和title
  // Map构造函数接受数组作为参数，实际上执行的是下面的算法。
  var items = [
    ['name', '张三'],
    ['title', 'Author']
  ];
  var map = new Map();
  items.forEach(([key, value]) => map.set(key, value))
  console.log('map=====', map)
  /**
   * 事实上，不仅仅是数组，任何具有Iterator接口，且每个成员都是一个双元素的数组的数据结构
   * 都可以当作Map构造函数的参数。这就是说，Set和Map都可以用来生成新的Map
  */
 var set = new Set([
   ['foo', 1],
   ['bar', 2]
 ]);
 console.log('set', set)
 var m1 = new Map(set);
 m1.get('foo') // 1
 var m2 = new Map([['baz', 3]]);
 var m3 = new Map(m2);
 m3.get('baz') // 3
 /**
  * 上面代码中，我们分别使用Set对象和Map对象，当作Map构造函数的参数，
  * 结果都生成了新的Map对象。
  * 如果对同一个键多次赋值，后面的值将会覆盖前面的值
  * 如果读取一个未知的键，则返回undefined
  *
  * 注意： 只有对同一个对象的引用，Map结构才将其视为同一个键，这一点要非常小心
 */
var map = new Map();
map.set(['a'], 555);
map.get(['a']); // undefined
/**
 * 上面代码的set和get方法，表面上是针对同一个键，但是实际上这是俩个不同的数组实例，内存地址是不一样的
 * 因此get方法无法读取该键，返回undefined
 * 同理，同样的值的俩个实例，在Map结构中被视为俩个键
*/
var map = new Map();
var k1 = ['a'];
var k2 = ['a'];
map.set(k1, 111).set(k2, 222);
console.log(map) // Map(2) {Array(1) => 111, Array(1) => 222}
map.get(k1); // 111
map.get(k2); // 222
/**
 * 上面代码中，变量k1和k2的值是一样的，但是它们在Map结构中被视为俩个键，
 * 由上可知，Map的键实际上是跟内存地址绑定的，只要内存地址不一样就视为俩个键，
 * 这就解决了同名属性碰撞(clash)的问题，我们扩展别人的库的时候，如果使用对象作为键名，就不用担心自己的水性与原作者的属性同名。

 *如果Map的键是一个简单类型的值(数字、字符串、布尔值), 则只有俩个值严格相等，Map将其视为一个键
 *比如0和-0就是一个键，布尔值true和字符串true则是俩个不同的键，另外，undefined和null也是俩个不同的键
 * 虽然NaN不严格相等于自身，但Map将其视为同一个键。
*/
var map = new Map();
map.set(-0, 123);
map.get(+0);// 123
map.set(true, 1);
map.set('true', 2);
map.get(true); // 1
map.set(undefined, 3);
map.set(null, 4);
map.get(undefined); // 3
map.set(NaN, 123);
map.get(NaN); // 123
// 实例的属性和操作方法： map结构的实例有以下属性和操作。
// 1. size属性返回Map结构的成员总数
var map = new Map();
map.set('foo', true);
map.set('bar', false);
map.size; // 2
// 2. Map.prototype.set(key, value)
// set方法设置键名key对应的键值为value,然后返回整个Map结构。如果key已经有值，则键值会被更新，否则就新生成该键
// set方法返回的是当前的Map对象，因此可以采用链式写法
var map = new Map().set(1, 'a').set(2, 'b').set(3, 'c');
// Map.prototype.get(key)
// get方法读取key对应的键值，如果找不到key，返回undefined
// Map.prototype.has(key) has方法返回一个布尔值，表示某个键是否在当前 Map 对象之中。
/**
 * （5）Map.prototype.delete(key)
 *  delete方法删除某个键，返回true。如果删除失败，返回false。
 *  （6）Map.prototype.clear()
 *   clear方法清除所有成员，没有返回值。
*/
// 遍历方法
// Map结构原生提供三个遍历器生成函数和一个遍历方法。
/**
 *  Map.prototype.keys()：返回键名的遍历器。
    Map.prototype.values()：返回键值的遍历器。
    Map.prototype.entries()：返回所有成员的遍历器。
    Map.prototype.forEach()：遍历 Map 的所有成员。
    需要特别注意的是，Map 的遍历顺序就是插入顺序。
*/
var map = new Map([
  ['F', 'no'],
  ['T', 'yes'],
])
for (let key of map.keys()) {
  console.log(key); // 'F' , 'T'
}
for (let value of map.values()) {
  console.log(value); // no  yes
}
for (let item of map.entries()) {
  console.log(item);
  // [['F', 'no'], ['T', 'yes']]
}
// 或者
for (let [key, value] of map.entries()) {
  console.log(key, value);
  // "F" "no"
  // "T" "yes"
}
// 等同于使用map.entries()
for (let [key, value] of map) {
  console.log(key, value);
  // "F" "no"
  // "T" "yes"
}
// 上面代码最后的那个例子，表示Map结构的默认遍历器接口(Symbol,iterator属性)，就是entries方法
map[Symbol.iterator] === map.entries; // true
// Map结构转为数组结构，比较快速的方法是使用扩展运算符
var map = new Map([
  [1, 'one'],
  [2, 'two'],
  [3, 'three'],
]);
[...map.keys()]; // [1,2,3]
[...map.values()];
// ['one', 'two', 'three']

[...map.entries()];
// [[1,'one'], [2, 'two'], [3, 'three']]
[...map];
// [[1,'one'], [2, 'two'], [3, 'three']]
// 结合数组的map方法、filter方法，可以实现Map的遍历和过滤(Map本身没有map和filter方法)
var map0 = new Map().set(1,'a').set(2,'b').set(3, 'c');
var map1 = new Map(
  [...map0].filter(([k, v]) => k < 3)
);
console.log('map1', map1)
// 产生Map结构 {1 => 'a', 2 => 'b'}
var map2 = new Map(
  [...map0].map(([k,v]) => [k * 2, '_' + v])
);
console.log('map2', map2);
// 产生 Map 结构 {2 => '_a', 4 => '_b', 6 => '_c'}
// 此外Map还有一个forEach方法，与数组的forEach方法类似，也可以实现遍历
map.forEach(function(value, key, map) {
  console.log('Key: %s, Value: %s', key, value)
});
// forEach方法还可以接受第二个参数，用来绑定this。
var reporter = {
  report: function(key, value) {
    console.log("Key: %s, Value: %s", key, value);
  }
};
map.forEach(function(value, key, map) {
  this.report(key, value);
}, reporter);
// 上面代码中，forEach方法的回调函数的this， 就指向reporter
</script>
<script>
  // 与其他数据结构的互相转换
  // (1) Map转为数组 前面已经提过，Map转为数组最方便的方法，就是使用扩展运算符(...)
  var myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
  [...myMap];
  // [ [ true, 7 ], [ { foo: 3 }, [ 'abc' ] ] ]
  // (2)数组转为Map 将数组传入Map构造函数，就可以转为Map
  new Map([
    [true, 7],
    [{foo: 3}, ['abc']]
  ]);
  // Map转为对象 如果所有Map的键都是字符串，它可以无损地转为对象。
  function strMapToObj(strMap) {
    var obj = Object.create(null);
    for(let [k,v] of strMap) {
      obj[k] = v;
    }
    return obj;
  }
  var myMap = new Map().set('yes', true).set('no', false);
  strMapToObj(myMap); // { yes: true, no: false }
  // 如果有非字符串的键名，那么这个键名会被转为字符串，再作为对象的键名
  // (4)对象转为Map 对象转为Map 可以通过Object.entries()
  var obj = {'a': 1, 'b': 2};
  var map = new Map(Object.entries(obj));
  // 此外，也可以自己实现一个转换函数
  function objToStrMap(obj) {
  let strMap = new Map();
  for (let k of Object.keys(obj)) {
    strMap.set(k, obj[k]);
  }
  return strMap;
}

objToStrMap({yes: true, no: false});
// Map {"yes" => true, "no" => false}
// (5)Map 转为JSON  Map 转为 JSON 要区分两种情况。一种情况是，Map 的键名都是字符串，这时可以选择转为对象 JSON。
function strMapToJson(strMap) {
  return JSON.stringify(strMapToObj(strMap));
}
var myMap = new Map().set('yes', true).set('no', false);
strMapToJson(myMap);
// '{"yes":true,"no":false}'
// 另一种情况是，Map的键名有非字符串，这时可以选择转为数组JSON
function mapToArrayJson(map) {
  return JSON.stringify([...map]);
}
var myMap = new Map().set(true, 7).set({foo: 3}, ['abc']);
mapToArrayJson(myMap)
// '[[true,7],[{"foo":3},["abc"]]]'
// (6) JSON转为Map JSON 转为 Map，正常情况下，所有键名都是字符串。
function jsonToStrMap(jsonStr) {
  return objToStrMap(JSON.parse(jsonStr));
}

jsonToStrMap('{"yes": true, "no": false}')
// Map {'yes' => true, 'no' => false}
// 但是，有一种特殊情况，整个 JSON 就是一个数组，
// 且每个数组成员本身，又是一个有两个成员的数组。
// 这时，它可以一一对应地转为 Map。这往往是 Map 转为数组 JSON 的逆操作。
function jsonToMap(jsonStr) {
  return new Map(JSON.parse(jsonStr));
}

jsonToMap('[[true,7],[{"foo":3},["abc"]]]')
// Map {true => 7, Object {foo: 3} => ['abc']}
</script>

<script>
  let obj2 = {}
  let myname = obj2
  const map8 = new Map()
  map8.set(obj2, "哈哈")
  console.log('888', map8)
 console.log('99999',  map8.get(obj2))
 console.log('99997',  map8.get(myname))
 console.log(map8.has(myname)) // true  存的是地址，俩个变量指向同一个地址，所以判断为true!
</script>
</body>
</html>